import XRegExp from 'xregexp';
import { decodeHtml } from '@/lib/tool';
import hljs from 'highlight.js';
import sanitizeHtml from 'sanitize-html';
import {
  type Dispatch,
  type MutableRefObject,
  type SetStateAction,
} from 'react';

export const handleEditorSave = async ({
  editor,
  isDirty,
  callback,
  setIsActive,
  setIsSaving,
}: {
  editor: any;
  isDirty: MutableRefObject<boolean>;
  callback: ({
    data,
    reset,
  }: {
    data: string;
    reset: () => void;
  }) => Promise<void>;
  setIsActive: Dispatch<SetStateAction<boolean>>;
  setIsSaving: Dispatch<SetStateAction<boolean>>;
}) => {
  const data = editor.getData();
  const pendingActions = editor.plugins.get('PendingActions');
  const action = pendingActions.add('Saving changes');

  const reset = () => {
    pendingActions.remove(action);

    if (data == editor.getData()) {
      isDirty.current = false;
    }

    handleUpdateStatus({ editor, isDirty, setIsActive, setIsSaving });
  };

  await callback({ data, reset });
};

export const handleEditorStatusChanges = ({
  editor,
  isDirty,
  setIsActive,
  setIsSaving,
  onChangeData = () => {},
}: {
  editor: any;
  isDirty: MutableRefObject<boolean>;
  setIsActive: Dispatch<SetStateAction<boolean>>;
  setIsSaving: Dispatch<SetStateAction<boolean>>;
  onChangeData?: () => void;
}) => {
  editor.plugins
    .get('PendingActions')
    .on('change:hasAny', () =>
      handleUpdateStatus({ editor, isDirty, setIsActive, setIsSaving }),
    );

  editor.model.document.on('change:data', () => {
    isDirty.current = true;
    handleUpdateStatus({ editor, isDirty, setIsActive, setIsSaving });
    onChangeData();
  });
};

export const handleEditorBeforeunload = ({ editor }: { editor: any }) => {
  const pendingActions = editor.plugins.get('PendingActions');

  window.addEventListener('beforeunload', (evt) => {
    if (pendingActions.hasAny) {
      evt.preventDefault();
    }
  });
};

export const handleCodeBlockHtml = (value: string) => {
  return XRegExp.replace(
    value,
    XRegExp(/<pre.*?><code.*?>([\s\S]*?)<\/code><\/pre>/gm),
    (...args) => {
      const args1 = typeof args[1] === 'string' ? args[1] : '// nodata';
      const code = decodeHtml(args1);
      const exec = /language-(\w+)/m.exec(
        typeof args[0] === 'string' ? args[0] : '',
      );
      const language = exec ? exec[1].trim() : 'plainText';
      const hCode = hljs.highlight(code, {
        language,
      }).value;
      return args[0].replace(
        args1,
        handleCodeBlockHighlight(handleCodeBlockLine(hCode)),
      );
    },
    'all',
  );
};

export const handleImageHtml = (value: string) => {
  return XRegExp.replace(
    value,
    XRegExp(/<img.*?>/gm),
    (...args) => {
      const exec = /src="(.*)"/g.exec(
        typeof args[0] === 'string' ? args[0] : '',
      );
      const src = exec ? exec[1].trim() : null;
      if (!src) {
        return '<img src="/images/logo.svg" alt="nodata" />';
      }

      const image = new Image();
      image.src = src;
      const width = image.width + '';
      const height = image.height + '';

      if (width === '0' || height === '0') {
        return args[0].replace(`src="${src}"`, `src="${src}"`);
      }
      return args[0].replace(
        `src="${src}"`,
        `src="${src}" width="${image.width}" height="${image.height}"`,
      );
    },
    'all',
  );
};

export const handleTitleAnchor = (value: string) => {
  const map = new Map();
  return XRegExp.replace(
    value,
    XRegExp(/<h(\d)\b[^>]*>(.*?)<\/h\d>/gm),
    (...args) => {
      const args1 = typeof args[1] === 'string' ? args[1] : '';
      const args2 = typeof args[2] === 'string' ? args[2] : '';
      if (!args1 || !args2) {
        return args[0] as any;
      }

      const level = parseInt(args1);
      const name = sanitizeHtml(args2, {
        allowedTags: [],
      })
        .split('')
        .map((item) => item.trim())
        .filter((item) => item)
        .join('')
        .replaceAll('.', '_')
        .replaceAll('、', '_')
        .replaceAll('，', '_')
        .replaceAll(',', '_')
        .replaceAll('：', '_')
        .replaceAll(':', '_')
        .replaceAll('/', '_');

      let id = `post-name-${level}-${name}`;
      let num = 0;
      while (map.has(id)) {
        num = num + 1;
        id = `post-name-${level}-${name}-${num}`;
      }

      const value = {
        id,
        level,
        name,
        children: [],
      };

      map.set(id, value);
      return args[0].replace(
        args2,
        `${args2}<a id="${name}" class="icon-link icon-link-hover text-decoration-none text-reset" href="#${name}"><i class="bi bi-hash invisible"></i></a>`,
      );
    },
    'all',
  );
};

export const handleTableHtml = (value: string) => {
  return XRegExp.replace(
    value,
    XRegExp(
      /<figure[^>]*class="[^"]*table[^"]*"[^>]*>[\s\S]*?<\/table><\/figure>/gm,
    ),
    (...args) => {
      let str = args[0] as any;
      const exec = XRegExp.exec(
        str,
        /<figure[^>]*class\s*=\s*["']([^"']+)["'][^>]*>/,
      );

      if (exec) {
        const classNames = exec[1]
          .split(/\s+/)
          .filter((className) => className !== 'table');

        if (!classNames.includes('table-responsive')) {
          classNames.push('table-responsive');
        }

        const replace = exec[0].replace(exec[1], classNames.join(' '));
        str = str.replace(exec[0], replace);
      }

      const exec2 = XRegExp.exec(
        str,
        /<table[^>]*class\s*=\s*["']([^"']+)["'][^>]*>/,
      );

      if (exec2) {
        const classNames = exec2[1].split(/\s+/);

        if (!classNames.includes('table')) {
          classNames.push('table');
        }

        const replace = exec2[0].replace(exec2[1], classNames.join(' '));
        str = str.replace(exec2[0], replace);
      } else {
        str = str.replace('<table', '<table class="table"');
      }

      return str;
    },
    'all',
  );
};

const handleCodeBlockLine = (code: string) => {
  let _index = 0;
  const split = code.split(/\r\n|\r|\n/gm);
  if (split.length < 2) {
    return code;
  }
  const newCode = split
    .map((value, index) => {
      if (value.includes('// h-start')) {
        return '<li class="visually-hidden user-select-none yw-code-li yw-code-li-h-start">// h-start</li>';
      } else if (value.includes('// h-end')) {
        return '<li class="visually-hidden user-select-none yw-code-li yw-code-li-h-end">// h-end</li>';
      }
      _index = _index + 1;
      return `<li class="list-unstyled d-flex gap-4 yw-code-li"><div class="user-select-none text-secondary yw-code-num yw-code-num-width text-end">${_index}</div><div>${
        value || '&nbsp;'
      }</div></li>`;
    })
    .join('');
  return `<ol class="ps-0 yw-code-ol">${newCode}</ol>`;
};

const handleCodeBlockHighlight = (code: string) => {
  return XRegExp.replace(
    code,
    XRegExp(/\/\/ h-start[\s\S]*?\/\/ h-end/gm),
    (...args) => {
      return typeof args[0] !== 'string'
        ? code
        : (args[0] as string).replaceAll(
            'yw-code-li',
            'yw-code-li bg-secondary-subtle',
          );
    },
    'all',
  );
};

const handleUpdateStatus = ({
  editor,
  isDirty,
  setIsActive,
  setIsSaving,
}: {
  editor: any;
  isDirty: MutableRefObject<boolean>;
  setIsActive: Dispatch<SetStateAction<boolean>>;
  setIsSaving: Dispatch<SetStateAction<boolean>>;
}) => {
  setIsActive(isDirty.current);
  setIsSaving(editor.plugins.get('PendingActions').hasAny);
};
